#pragma once

#include <boost/detail/endian.hpp>
#include <boost/type_traits.hpp>

#include "typedefs.hpp"




namespace boostext
{
	/**
	 * Defines the different types of endian ordering.
	 */
	enum endian_ordering
	{
		little_endian_ordering,
		big_endian_ordering,

		network_endian_ordering = big_endian_ordering,

		#ifdef BOOST_LITTLE_ENDIAN
		system_endian_ordering = little_endian_ordering,
		#endif

		#ifdef BOOST_BIG_ENDIAN
		system_endian_ordering = big_endian_ordering,
		#endif
	};
	////////////////////////////////////////////////////////////////////////////////////////////////




	template <typename T>
	class big_endian
	{
		// We only accept integers, chars or floating-point types
		// We do not accept references
		// We must be of the same size, as the original type
		BOOST_STATIC_ASSERT( boost::is_arithmetic<T>::value == true );
		BOOST_STATIC_ASSERT( boost::is_reference<T>::value == false );

	private:

		T   s_;

	public:

		big_endian() { BOOST_STATIC_ASSERT( sizeof( big_endian<T> ) == sizeof( T ) ); }
		big_endian(T s) { BOOST_STATIC_ASSERT( sizeof( big_endian<T> ) == sizeof( T ) ); store(s); }

		big_endian& operator=(T s) { store(s); return *this; }

		operator T() const { return get(); }

		/**
		 * Treat the given value as if it was coming from a system, that stores
		 * values in big endian ordering. The resulting big_enum object exactly
		 * stores the value (using the same byte ordering, as given), but converts
		 * it (when necessary) when the user wants to access the value.
		 */
		static big_endian<T> raw(T s)
		{
			// The class for itself verifies during compilation
			// that sizeof(tmp) == sizeof(T)
			big_endian<T> tmp;
			memcpy(&tmp,&s,sizeof(T));
			return tmp;
		}

	private:

		void store(T s)
		{
			s_ = s;
			#ifdef BOOST_LITTLE_ENDIAN
				endian_swap(s_);
			#endif
		}

		T get() const
		{
			T tmp = s_;
			#ifdef BOOST_LITTLE_ENDIAN
				endian_swap(tmp);
			#endif

			return tmp;
		}
	};
	////////////////////////////////////////////////////////////////////////////////////////////////

	template <typename T>
	class little_endian
	{
		// We only accept integers, chars or floating-point types
		// We do not accept references
		// We must be of the same size, as the original type
		BOOST_STATIC_ASSERT( boost::is_arithmetic<T>::value == true );
		BOOST_STATIC_ASSERT( boost::is_reference<T>::value == false );

	private:

		T s_;

	public:

		little_endian() { BOOST_STATIC_ASSERT( sizeof( little_endian<T> ) == sizeof( T ) ); }
		little_endian(T s) { BOOST_STATIC_ASSERT( sizeof( little_endian<T> ) == sizeof( T ) ); store(s); }

		little_endian& operator=(T s) { store(s); return *this; }

		operator T() const { return get(); }

		/**
		 * Treat the given value as if it was coming from a system, that stores
		 * values in little endian ordering. The resulting little_enum object exactly
		 * stores the value (using the same byte ordering, as given), but converts
		 * it (when necessary) when the user wants to access the value.
		 */
		static little_endian<T> raw(T s)
		{
			// The class for itself verifies during compilation
			// that sizeof(tmp) == sizeof(T)
			little_endian<T> tmp;
			memcpy(&tmp,&s,sizeof(T));
			return tmp;
		}

	private:

		void store(T s)
		{
			s_ = s;
			#ifdef BOOST_BIG_ENDIAN
				endian_swap(s_);
			#endif
		}

		T get() const
		{
			T tmp = s_;
			#ifdef BOOST_BIG_ENDIAN
				endian_swap(tmp);
			#endif

			return tmp;
		}
	};
	///////////////////////////////////////////////////////////////////////////////////////////////




	/**
	 * Declaration of the endian_swap function.
	 * It is specialized for the arithmetic types:
	 * - unsigned short
	 * - signed short
	 * - unsigned int
	 * - signed int
	 * - unsigned __int64
	 * - signed __int64
	 * - float
	 * - double
	 */
	template <typename T>
	inline void endian_swap(T &);

	template <>
	inline void endian_swap<uint16>(uint16& n)
	{
		BOOST_STATIC_ASSERT( sizeof( uint16 ) == 2 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[1] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<int16>(int16& n)
	{
		BOOST_STATIC_ASSERT( sizeof( int16 ) == 2 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[1] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<uint32>(uint32& n)
	{
		BOOST_STATIC_ASSERT( sizeof( uint32 ) == 4 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[3] );
		std::swap( b[1], b[2] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<int32>(int32& n)
	{
		BOOST_STATIC_ASSERT( sizeof( int32 ) == 4 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[3] );
		std::swap( b[1], b[2] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<uint64>(uint64& n)
	{
		BOOST_STATIC_ASSERT( sizeof( uint64 ) == 8 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[7] );
		std::swap( b[1], b[6] );
		std::swap( b[2], b[5] );
		std::swap( b[3], b[4] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<int64>(int64& n)
	{
		BOOST_STATIC_ASSERT( sizeof( int64 ) == 8 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[7] );
		std::swap( b[1], b[6] );
		std::swap( b[2], b[5] );
		std::swap( b[3], b[4] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<float>(float& n)
	{
		BOOST_STATIC_ASSERT( sizeof( float ) == 4 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[3] );
		std::swap( b[1], b[2] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////

	template <>
	inline void endian_swap<double>(double& n)
	{
		BOOST_STATIC_ASSERT( sizeof( double ) == 8 );

		byte *b = reinterpret_cast<byte*>(&n);
		std::swap( b[0], b[7] );
		std::swap( b[1], b[6] );
		std::swap( b[2], b[5] );
		std::swap( b[3], b[4] );
	}
	///////////////////////////////////////////////////////////////////////////////////////////////
}
///////////////////////////////////////////////////////////////////////////////////////////////////
